using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Lizk.SimpleHUD;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Font=System.Drawing.Font;
using System.Reflection;
using System.Collections;
using System.Xml;
using System.Threading;
using Lizk.DShowPlayer;
using Lizk.Utilities;
using System.ComponentModel;

namespace SharpMedia.MDX
{

    public class MDXRenderer : HUDRenderer
    {
        private MDXForm form;
        private Sprite mainDrawSprite, stringCacheSprite;
        private MDXImage whiteTex;
        private object videoListLock = new object();
        private Dictionary<string, List<ImageContainer>> fonts = new Dictionary<string, List<ImageContainer>>();
        private Dictionary<string, ImageContainer> images = new Dictionary<string, ImageContainer>();
        private Dictionary<string, MDXVideo> videos = new Dictionary<string, MDXVideo>();

        private Dictionary<HUDLabel, StringCache> stringCache = new Dictionary<HUDLabel, StringCache>();
        public string FontRootDir { get; set; }
        public string ImageRoot { get; set; }
        public bool Unloading { get; set; }
        public TimeSpan GarbageCollectionInterval { get; set; }

        public MDXRenderer(MDXForm form)
        {
            this.form = form;

            form.EndingProgram += new EventHandler(delegate { UnloadUnmanagedResources(false); });
            form.AfterReset += new EventHandler(delegate { LoadGraphics(); });
            form.BeforeReset += new CancelEventHandler(delegate (object o, CancelEventArgs args)
                {
                        UnloadGraphics();
                });

            form.WindowsMessages += new MDXForm.WndProcHandler(form_WindowsMessages);
        }

        void form_WindowsMessages(ref Message m, ref bool callBase)
        {
            foreach (var video in videos)
            {
                if(video.Value.Player != null)
                    video.Value.Player.PreFilterMessage(ref m);
            }
        }

        ~MDXRenderer()
        {
            Dispose();
        }
        /// <summary>
        /// Garbage collection disposes 
        /// </summary>
        public void GarbageCollect()
        {
            // Lists are because its a big nono to change  a collection within a foreach.
            // I need to think of anotherway to do this but it can wait.
            List<string> deletedImages = new List<string>();
            List<string> deletedFonts = new List<string>();
            List<HUDLabel> deletedHudFonts = new List<HUDLabel>();
            DateTime now = DateTime.Now;

            foreach (KeyValuePair<string, ImageContainer> kvp in images)
            {
                if (now.Subtract(kvp.Value.LastUsed) > GarbageCollectionInterval)
                    deletedImages.Add(kvp.Key);
            }
            
            foreach (KeyValuePair<string, List<ImageContainer>> kvp in fonts)
            {
                bool aNewOne = false;
                foreach (ImageContainer ic in kvp.Value)
                {
                    if (now.Subtract(ic.LastUsed) < GarbageCollectionInterval)
                        aNewOne = true;
                }
                if (aNewOne == false)
                {
                    deletedFonts.Add(kvp.Key);
                }
            }
            
            foreach (KeyValuePair<HUDLabel, StringCache> kvp in stringCache)
            {
                if (kvp.Value.image == null || now.Subtract(kvp.Value.image.LastUsed) > GarbageCollectionInterval)
                {
                    deletedHudFonts.Add(kvp.Key);
                }
            }

            foreach (HUDLabel fc in deletedHudFonts)
            {
                if (stringCache[fc].image != null)
                {
                    stringCache[fc].image.Texture.Dispose();
                    stringCache[fc].image.Texture = null;
                }

                stringCache.Remove(fc);
            }
            foreach (string hf in deletedFonts)
            {
                foreach (ImageContainer ic in fonts[hf])
                {
                    ic.Image.Dispose();
                }
                fonts.Remove(hf);
            }
            foreach (string fc in deletedImages)
            {
                images[fc].Image.Dispose();
                images.Remove(fc);
            }
        }

        /// <summary>
        /// The textures used for saving string are unmanaged as well as the allocator for the video player.
        /// </summary>
        public void UnloadUnmanagedResources(bool restart)
        {
            lock (videoListLock)
            {
                List<MDXVideo> vids = new List<MDXVideo>(videos.Values);
                foreach (MDXVideo vc in vids)
                {
                    //StopVideo(vc, restart);
                    if(!restart)
                        vc.video.Stop();
                    
                    vc.Unload();
                    if (vc.Disposed)
                    {
                        if (videos.ContainsKey(VideoKey(vc.video)))
                            videos.Remove(VideoKey(vc.video));
                    }
                }
            }
            foreach (StringCache t in stringCache.Values)
            {
                if(t.image.Texture != null)
                    t.image.Texture.Dispose();
                t.image.Texture = null;
            }
            stringCache.Clear();
        }

        public bool VideoPlaying { get { return videos.Count > 0; } }

        public string ImagePath(string image)
        {
            if (!File.Exists(image))
            {
                string test = Path.Combine(ImageRoot, image);
                if (File.Exists(test))
                    return test;
            }
            return image;
            
        }

        public bool ImageLoaded(HUDImage image)
        {
            if (string.IsNullOrEmpty(image.File))
                return false;
            
            if (images.ContainsKey(Path.GetFullPath(ImagePath(image.File)).ToLower()))
            {
                if (image.ImageSize == RectangleS.Empty && images[Path.GetFullPath(ImagePath(image.File)).ToLower()].Image.IsLoaded)
                {
                    image.ImageSize = new RectangleS(images[Path.GetFullPath(ImagePath(image.File)).ToLower()].Image.TextureSize.X, images[Path.GetFullPath(ImagePath(image.File)).ToLower()].Image.TextureSize.Y, images[Path.GetFullPath(ImagePath(image.File)).ToLower()].Image.TextureSize.Width, images[Path.GetFullPath(ImagePath(image.File)).ToLower()].Image.TextureSize.Height);
                    if (!image.IsFading && image.FadeOnLoad)
                    {
                        byte alpha = (byte)image.Alpha;
                        image.Alpha = 1;
                        image.Fade(alpha, 6f);
                    }
                }
                return true;
            }
            else
                return false;
        }

        public void LoadImage(HUDImage image)
        {
            if (string.IsNullOrEmpty(image.File))
                return;
            if (!File.Exists(ImagePath(image.File)))
                return;

            MDXImage img = MDXImage.MDXImageLoader.FromFile(form.GraphicsDevice, Path.GetFullPath(ImagePath(image.File)).ToLower(), null, Size.Empty);
                images.Add(Path.GetFullPath(ImagePath(image.File)).ToLower(), new ImageContainer(img));
        }

        public void UnloadImage(HUDImage image)
        {
            if (string.IsNullOrEmpty(image.File) || string.IsNullOrEmpty(ImagePath(image.File)))
                return;
            if (images.ContainsKey(Path.GetFullPath(ImagePath(image.File)).ToLower()))
            {
                if (images[Path.GetFullPath(ImagePath(image.File)).ToLower()] != null)
                    images[Path.GetFullPath(ImagePath(image.File)).ToLower()].Image.Dispose();
                images.Remove(Path.GetFullPath(ImagePath(image.File)).ToLower());
            }
        }

        public void PaintImage(RectangleS bounds, RectangleS source, byte alpha, HUDImage image)
        {
            string imageKey = Path.GetFullPath(ImagePath(image.File)).ToLower();
            
            if (images.ContainsKey(imageKey) && images[imageKey].Image.IsLoaded)
            {

                if (image.EffectTag == null)
                {
                    MDXImage mimage = images[imageKey].Image;

                    Rectangle sourceRect = new Rectangle((int)source.X + mimage.TextureSize.X, (int)source.Y + mimage.TextureSize.Y, (int)Math.Ceiling(source.Width), (int)Math.Ceiling(source.Height));
                    if (sourceRect == Rectangle.Empty)
                        return;

                    
                    mainDrawSprite.Begin(SpriteFlags.AlphaBlend | SpriteFlags.DoNotSaveState);
                    // Shader hax
                    //                int numpasses = engine.effect.Begin(0);
                    //                engine.effect.BeginPass(0);


                    mainDrawSprite.Draw2D(mimage.Texture, sourceRect, new SizeF(bounds.Width, bounds.Height), PointF.Empty, 0f, new PointF(bounds.Location.X, bounds.Location.Y), System.Drawing.Color.FromArgb(alpha, System.Drawing.Color.FromArgb(image.Color.A, image.Color.R, image.Color.G, image.Color.B)));
                    mainDrawSprite.End();
                    // Shader HAX 
                    //                testEffect.EndPass();
                    //                testEffect.End();
                }

            }
        }

        public SizeS GetCanvasSize()
        {
            return new SizeS(form.CurrentSize.Width, form.CurrentSize.Height);
        }

        public void LoadGraphics()
        {
            mainDrawSprite = new Sprite(form.GraphicsDevice);
            stringCacheSprite = new Sprite(form.GraphicsDevice);
            whiteTex = MDXImage.MDXImageLoader.FromFile(form.GraphicsDevice, Path.GetFullPath(ImagePath("white.png")).ToLower(), null, Size.Empty);

        }

        public void UnloadGraphics()
        {
            UnloadUnmanagedResources(true);
            if (mainDrawSprite != null)
            {
                mainDrawSprite.Dispose();
                mainDrawSprite = null;
            }
            if (stringCacheSprite != null)
            {
                stringCacheSprite.Dispose();
                stringCacheSprite = null;
            }
            if (whiteTex != null)
            {
                whiteTex.Dispose();
                whiteTex = null;
            }
        }

        public void BeginDraw()
        {
            form.GraphicsDevice.Clear(ClearFlags.Target, Color.Black, 1, 0);
            
            foreach (MDXVideo v in videos.Values)
            {
                v.UpdateTexture();
            }
        }

        public void HandleVideoCommandQueue()
        {
            if (!form.DeviceAvailable())
                return;
                foreach (MDXVideo video in new List<MDXVideo>(videos.Values))
                {
                    if (video.Disposed)
                    {
                        if (videos.ContainsKey(VideoKey(video.video)))
                            videos.Remove(VideoKey(video.video));
                    }
                    video.Update();
                }

        }

        public void EndDraw()
        {

        }

        public void UnloadContainer(HUDControl cont)
        {
            if (!(cont is ICollection))
                return;

            foreach (HUDControl hc in (ICollection)cont)
            {
                if ((hc is ICollection))
                {
                    UnloadContainer(hc);
                }
                else if (hc is HUDImage)
                {
                    
                    UnloadImage((HUDImage)hc);
                }
                else if (hc is HUDLabel)
                {
                    HUDLabel fc = (HUDLabel)hc;
                    if (stringCache.ContainsKey(fc))
                    {
                        if (stringCache[fc].image.Texture != null)
                            stringCache[fc].image.Texture.Dispose();
                    }

                    stringCache.Remove(fc);
                }
            }
        }

        public void Dispose()
        {
            if (mainDrawSprite != null)
            {
                mainDrawSprite.Dispose();
                mainDrawSprite = null;
            }
            if (stringCacheSprite != null)
            {
                stringCacheSprite.Dispose();
                stringCacheSprite = null;
            }
            if (whiteTex != null)
            {
                whiteTex.Dispose();
                whiteTex = null;
            }
            foreach (ImageContainer image in images.Values)
            {
                image.Image.Dispose();
            }
            images.Clear();

            foreach (StringCache f in stringCache.Values)
            {
                if(f.image.Texture != null)
                    f.image.Texture.Dispose();
            }
            stringCache.Clear();

            foreach (KeyValuePair<string, List<ImageContainer>> kvp in fonts)
            {
                foreach (ImageContainer i in kvp.Value)
                {
                    i.Image.Dispose();
                }
            }
            fonts.Clear();
        }

        #region HUDRenderer Members

        
        public void PaintText(RectangleS bounds, RectangleS rectangleS, int a, HUDLabel label, int page)
        {
            if (fonts.ContainsKey(label.Font) && fonts[label.Font][page].Image.IsLoaded)
            {
                if (!stringCache.ContainsKey(label) || !StringCache.FromLabel(label).Equals(stringCache[label]))
                {
                    form.Wait();
                    CacheLabel(label);
                    form.Resume();
                }




                if (stringCache.ContainsKey(label) && stringCache[label] != null && stringCache[label].image.Texture != null && !stringCache[label].image.Texture.Disposed)
                {

                    RectangleS dest = RectangleS.Intersect(rectangleS, stringCache[label].image.Source);
                    if (dest == RectangleS.Empty)
                        return;



                    RectangleS source = RectangleS.Intersect(stringCache[label].image.Source, dest);//new Rectangle((int)(stringCache[fc].Source.X - dest.X), (int)(stringCache[fc].Source.Y - dest.Y), (int)dest.Width, (int)dest.Height);
                    source.Location -= stringCache[label].image.Source.Location;


                    if (source.Size == SizeS.Empty)
                        return;

                    mainDrawSprite.Begin(SpriteFlags.AlphaBlend | SpriteFlags.DoNotSaveState);

                    
                    mainDrawSprite.Draw2D(stringCache[label].image.Texture, /*new Rectangle((int)rectangleS.X, (int)rectangleS.Y, (int)rectangleS.Width, (int)rectangleS.Height)*/ C(source), new SizeF(source.Width, source.Height), PointF.Empty, 0f, new PointF((int)Math.Round(dest.Location.X) + (int)Math.Round(bounds.Left) - (int)Math.Round(rectangleS.X), (int)Math.Round(dest.Location.Y) + (int)Math.Round(bounds.Top) - (int)Math.Round(rectangleS.Y)), System.Drawing.Color.FromArgb(a, System.Drawing.Color.FromArgb(label.Color.A, label.Color.R, label.Color.G, label.Color.B)));
                    mainDrawSprite.End();
                }
            }
        }

        public void LoadFont(HUDFont hUDFont)
        {
            string fontPath = Path.Combine(FontRootDir, hUDFont.Name + ".fnt");
            if (!File.Exists(fontPath))
                return;
            if (!hUDFont.IsLoaded)
            {
                XmlDocument f = new XmlDocument();
                f.Load(fontPath);
                hUDFont.LoadFromFile(f);
            }
            fonts.Add(hUDFont.Name, new List<ImageContainer>());
            foreach (string s in hUDFont.Pages.Values)
            {
                string pagePath = Path.Combine(Path.GetDirectoryName(fontPath), s);
                
                if (!File.Exists(pagePath))
                    return;

                MDXImage img = MDXImage.MDXImageLoader.FromFile(form.GraphicsDevice, Path.GetFullPath(pagePath).ToLower(), null, Size.Empty);
                fonts[hUDFont.Name].Add(new ImageContainer(img));
            }
        }

        public bool HasFont(HUDFont hUDFont)
        {
            return fonts.ContainsKey(hUDFont.Name);
        }

        private void CacheLabel(HUDLabel label)
        {
            StringCache fc = StringCache.FromLabel(label);

            if (label.Size == SizeS.Empty || string.IsNullOrEmpty(label.Text) || !HUDFont.GetFont(label.Font).IsLoaded || !form.DeviceAvailable())
                return;
            foreach (ImageContainer i in fonts[label.Font])
            {
                if (!i.Image.IsLoaded)
                    return;
            }




            if (stringCache.ContainsKey(label))
            {
                if (stringCache[label].image.Texture != null)
                {
                    stringCache[label].image.Texture.Dispose();
                    stringCache[label].image.Texture = null;
                }

                stringCache.Remove(label);
            } 
            fc.image = new TextureContainer(null);
            stringCache.Add(label, fc);


            float minX = float.MaxValue, maxX = 0, minY = float.MaxValue, maxY = 0;

            foreach (HUDFont.DrawInfo di in label.GetCharInfo())
            {
                if (di.Destination.Left < minX)
                    minX = (int)di.Destination.Left;
                if (di.Destination.Right > maxX)
                    maxX = (int)di.Destination.Right;
                if (di.Destination.Top < minY)
                    minY = (int)di.Destination.Top;
                if (di.Destination.Bottom > maxY)
                    maxY = (int)di.Destination.Bottom;

            }
            if (label.GetCharInfo().Count == 0)
                return;
            RectangleS drawBounds = new RectangleS(minX, minY, maxX - minX, maxY - minY);
            label.SourceRectangle = stringCache[label].image.Source = drawBounds;

            int texX = (int)drawBounds.Width, texY = (int)drawBounds.Height;

            if (form.GraphicsDevice.DeviceCaps.TextureCaps.SupportsPower2)
            {
                int adjustedX = 1, adjustedY = 1;
                while (adjustedX < drawBounds.Width)
                    adjustedX = adjustedX << 1;

                while (adjustedY < drawBounds.Height)
                    adjustedY = adjustedY << 1;
                texX = adjustedX;
                texY = adjustedY;
            }
            
            // Then create a private texture for the client application
            Texture privateTexture = new Texture(
                form.GraphicsDevice,
                texX,
                texY,
                1,
                Usage.RenderTarget,
                Format.A8R8G8B8,
                Pool.Default
                );
            
            Surface f = privateTexture.GetSurfaceLevel(0);
            stringCache[label].image.Texture = privateTexture;

            Surface renderTarget = form.GraphicsDevice.GetRenderTarget(0);
            form.GraphicsDevice.SetRenderTarget(0, f);

            form.GraphicsDevice.Clear(ClearFlags.Target, System.Drawing.Color.FromArgb(0, label.Color.R, label.Color.G, label.Color.B), 0, 0);
            stringCacheSprite.Begin(SpriteFlags.AlphaBlend | SpriteFlags.DoNotSaveState);
            foreach (HUDFont.DrawInfo di in label.GetCharInfo())
            {
                stringCacheSprite.Draw2D(fonts[label.Font][di.Page].Image.Texture, C(di.Source), F(di.Destination.Size), PointF.Empty, 0f, F(di.Destination.Location- drawBounds.Location), System.Drawing.Color.FromArgb(255, label.Color.R, label.Color.G, label.Color.B));
            }
            stringCacheSprite.End();
            form.GraphicsDevice.SetRenderTarget(0, renderTarget);
            
            f.Dispose();
            renderTarget.Dispose();
        }

        #endregion

        public static Size C(SizeS c) { return new Size((int)c.Width, (int)c.Height); }
        public static Point C(PointS c) { return new Point((int)c.X, (int)c.Y); }
        public static Rectangle C(RectangleS c) { return new Rectangle((int)c.X, (int)c.Y, (int)c.Width, (int)c.Height); }
        public static SizeF F(SizeS c) { return new SizeF(c.Width, c.Height); }
        public static PointF F(PointS c) { return new PointF(c.X, c.Y); }
        public static RectangleF F(RectangleS c) { return new RectangleF(c.X, c.Y, c.Width, c.Height); }



        public void UnloadFont(HUDFont hudFont)
        {
        
        }

        public void PaintVideo(RectangleS bounds, RectangleS source, byte alpha, HUDVideo2 video)
        {
            string key = Path.GetFullPath(video.SourceUrl).ToLower();

            if (videos.ContainsKey(key) && videos[key].Disposed)
            {
                lock (videoListLock)
                {
                    videos.Remove(key);
                }
            }

            if (videos.ContainsKey(key) && videos[key].restart)
            {
                videos[key].Restart();
            }
            if (videos.ContainsKey(key) && videos[key].allocator != null && videos[key].Texture != null && !videos[key].Texture.Disposed)
            {

                mainDrawSprite.Begin(SpriteFlags.AlphaBlend | SpriteFlags.DoNotSaveState);
                // Shader hax
                //                    int numpasses = engine.effect.Begin(0);
                //                    engine.effect.BeginPass(0);
                mainDrawSprite.Draw2D(videos[key].Texture, new Rectangle((int)source.X, (int)source.Y, (int)Math.Floor(source.Width), (int)Math.Floor(source.Height)), new SizeF(bounds.Width, bounds.Height), PointF.Empty, 0f, new PointF(bounds.Location.X, bounds.Location.Y), System.Drawing.Color.FromArgb(alpha, System.Drawing.Color.White));
                mainDrawSprite.End();
                // More Shader HAX
                //                    engine.effect.EndPass();
                //                    engine.effect.End();
            }
        }

        public void EnterContainer(RectangleS activeArea)
        {
        }

        public void LeaveContainer(RectangleS activeArea)
        {

        }

        private string VideoKey(HUDVideo2 video)
        {
            if (video.SourceUrl.StartsWith(@"\\tv\"))
            {
                return video.SourceUrl;
            }
            else
            {
                return Path.GetFullPath(video.SourceUrl).ToLower();
            }
        }

        public bool VideoLoaded(HUDVideo2 video)
        {
            lock (videoListLock)
            {
                if (videos.ContainsKey(VideoKey(video)) && videos[VideoKey(video)].Disposed)
                {
                    lock (videoListLock)
                    {
                        videos.Remove(VideoKey(video));
                    }
                }
                return videos.ContainsKey(VideoKey(video));
            }
        }

        public void LoadVideo(HUDVideo2 video)
        {
            if (video.Closed)
                return;
            lock (videoListLock)
            {

                if (video.SourceUrl.StartsWith(@"\\tv\"))
                {
                    videos.Add(VideoKey(video), new MDXVideoTV(form, video));
                }
                else if (File.Exists(video.SourceUrl))
                {
                    if (video.SourceUrl.ToLower().EndsWith(".ifo"))
                    {
                        videos.Add(VideoKey(video), new MDXVideoDVD(form, video));
                    }
                    else
                    {
                        videos.Add(VideoKey(video), new MDXVideoFile(form, video));
                    }
                }
            }
       }

        public void UnloadVideo(HUDVideo2 video)
        {
            video.Close();
            video.Updater.SetState(HUDVideo2.PlayerState.NotLoaded);
        }


        public void Reset()
        {
            
        }

        public void Initialize()
        {
            
        }



        private class TextureContainer
        {
            private Texture texture;
            private DateTime lastUsed = DateTime.Now;
            private RectangleS source;

            public RectangleS Source
            {
                get { return source; }
                set { source = value; }
            }

            public DateTime LastUsed
            {
                get { return lastUsed; }
            }
            public Texture Texture
            {
                get
                {
                    lastUsed = DateTime.Now;
                    return texture;
                }
                set { texture = value; }
            }

            public TextureContainer(Texture t)
            {
                this.texture = t;
            }
        }


        private class ImageContainer
        {
            private MDXImage image;
            private DateTime lastUsed = DateTime.Now;
            public DateTime LastUsed
            {
                get { return lastUsed; }
            }
            public MDXImage Image
            {
                get
                {
                    lastUsed = DateTime.Now;
                    return image;
                }
                set { image = value; }
            }
            public ImageContainer(MDXImage image)
            {
                this.image = image;
            }
        }

        private class StringCache
        {
            public string font;
            public Lizk.SimpleHUD.Alignment alignment;
            public string text;
            public SizeS size;
            public TextureContainer image;
            public StringCache(string font, Lizk.SimpleHUD.Alignment alignment, string text, SizeS size)
            {
                this.font = font;
                this.alignment = alignment;
                this.text = text;
                this.size = size;
                this.image = null;
            }

            public static StringCache FromLabel(HUDLabel label)
            {
                return new StringCache(label.Font, label.Alignment, label.Text, label.Size);
            }

            public override bool Equals(object obj)
            {
                if (obj is StringCache)
                {
                    StringCache fc = (StringCache)obj;
                    return fc.font == font && fc.alignment == alignment && fc.text == text && size == fc.size;
                }
                else
                    return base.Equals(obj);
            }
            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

    }




}



